From 3ef4a3d46fad3cff1d44e72e49379cf1aa0e038c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Jun 2011 01:41:18 +0200 Subject: [PATCH] border-image: Redo border-image rendering The new code is smaller, less crashy and correct(er), but arguably more complex. I'd have liked to make it simpler, but this border image algorithm is complex... --- gtk/gtkborderimage.c | 536 +++++++++++++++---------------------------- 1 file changed, 189 insertions(+), 347 deletions(-) diff --git a/gtk/gtkborderimage.c b/gtk/gtkborderimage.c index 499bbe42d3..277007857f 100644 --- a/gtk/gtkborderimage.c +++ b/gtk/gtkborderimage.c @@ -188,240 +188,167 @@ _gtk_border_image_pack (GValue *value, g_free (repeat); } -static void -render_corner (cairo_t *cr, - gdouble corner_x, - gdouble corner_y, - gdouble corner_width, - gdouble corner_height, - cairo_surface_t *surface, - gdouble image_width, - gdouble image_height) -{ - if (corner_width == 0 || corner_height == 0) - return; - - cairo_save (cr); - - cairo_rectangle (cr, corner_x, corner_y, corner_width, corner_height); - - cairo_translate (cr, corner_x, corner_y); - cairo_scale (cr, - corner_width / image_width, - corner_height / image_height); - - cairo_set_source_surface (cr, surface, 0, 0); - cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD); - - cairo_fill (cr); - - cairo_restore (cr); -} +typedef struct _GtkBorderImageSliceSize GtkBorderImageSliceSize; +struct _GtkBorderImageSliceSize { + double offset; + double size; +}; -static cairo_surface_t * -create_spaced_surface (cairo_surface_t *tile, - gdouble tile_width, - gdouble tile_height, - gdouble width, - gdouble height, - GtkOrientation orientation) +static void +gtk_border_image_compute_border_size (GtkBorderImageSliceSize sizes[3], + double offset, + double area_size, + int start_border, + int end_border) { - gint n_repeats, idx; - gdouble avail_space, step; - cairo_surface_t *retval; - cairo_t *cr; - - n_repeats = (orientation == GTK_ORIENTATION_HORIZONTAL) ? - (gint) floor (width / tile_width) : - (gint) floor (height / tile_height); - - avail_space = (orientation == GTK_ORIENTATION_HORIZONTAL) ? - width - (n_repeats * tile_width) : - height - (n_repeats * tile_height); - step = avail_space / (n_repeats + 2); - - retval = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - width, height); - cr = cairo_create (retval); - idx = 0; - - while (idx < n_repeats) - { - cairo_save (cr); - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - cairo_set_source_surface (cr, tile, - ((idx + 1) * step) + (idx * tile_width), 0); - else - cairo_set_source_surface (cr, tile, - 0, ((idx + 1 ) * step) + (idx * tile_height)); - - cairo_paint (cr); - cairo_restore (cr); - - idx++; - } - - cairo_destroy (cr); - - return retval; + /* This code assumes area_size >= start_border + end_border */ + + sizes[0].offset = offset; + sizes[0].size = start_border; + sizes[1].offset = offset + start_border; + sizes[1].size = area_size - start_border - end_border; + sizes[2].offset = offset + area_size - end_border; + sizes[2].size = end_border; } static void -render_border (cairo_t *cr, - gdouble total_width, - gdouble total_height, - cairo_surface_t *surface, - gdouble surface_width, - gdouble surface_height, - guint side, - GtkBorder *border_area, - GtkCssBorderImageRepeat *repeat) +gtk_border_image_render_slice (cairo_t *cr, + cairo_surface_t *slice, + double slice_width, + double slice_height, + double x, + double y, + double width, + double height, + GtkCssRepeatStyle hrepeat, + GtkCssRepeatStyle vrepeat) { - gdouble target_x, target_y; - gdouble target_width, target_height; - GdkRectangle image_area; + double hscale, vscale; + double xstep, ystep; + cairo_extend_t extend = CAIRO_EXTEND_PAD; + cairo_matrix_t matrix; cairo_pattern_t *pattern; - gboolean repeat_pattern; - if (surface == NULL) - return; + /* We can't draw center tiles yet */ + g_assert (hrepeat == GTK_CSS_REPEAT_STYLE_NONE || vrepeat == GTK_CSS_REPEAT_STYLE_NONE); - cairo_surface_reference (surface); - repeat_pattern = FALSE; + hscale = width / slice_width; + vscale = height / slice_height; + xstep = width; + ystep = height; - if (side == SIDE_TOP || side == SIDE_BOTTOM) + switch (hrepeat) { - target_height = (side == SIDE_TOP) ? (border_area->top) : (border_area->bottom); - target_width = surface_width * (target_height / surface_height); - } - else - { - target_width = (side == SIDE_LEFT) ? (border_area->left) : (border_area->right); - target_height = surface_height * (target_width / surface_width); + case GTK_CSS_REPEAT_STYLE_REPEAT: + extend = CAIRO_EXTEND_REPEAT; + hscale = vscale; + break; + case GTK_CSS_REPEAT_STYLE_SPACE: + { + double space, n; + + extend = CAIRO_EXTEND_NONE; + hscale = vscale; + + xstep = hscale * slice_width; + n = floor (width / xstep); + space = (width - n * xstep) / (n + 1); + xstep += space; + x += space; + width -= 2 * space; + } + break; + case GTK_CSS_REPEAT_STYLE_NONE: + break; + case GTK_CSS_REPEAT_STYLE_ROUND: + extend = CAIRO_EXTEND_REPEAT; + hscale = width / (slice_width * MAX (round (width / (slice_width * vscale)), 1)); + break; + default: + g_assert_not_reached (); + break; } - if (side == SIDE_TOP || side == SIDE_BOTTOM) + switch (vrepeat) { - image_area.x = border_area->left; - image_area.y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom); - image_area.width = total_width - border_area->left - border_area->right; - image_area.height = (side == SIDE_TOP) ? border_area->top : border_area->bottom; - - target_x = border_area->left; - target_y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom); - - if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_NONE) - { - target_width = image_area.width; - } - else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT) - { - repeat_pattern = TRUE; - - target_x = border_area->left + (total_width - border_area->left - border_area->right) / 2; - target_y = ((side == SIDE_TOP) ? 0 : (total_height - border_area->bottom)) / 2; - } - else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_ROUND) - { - gint n_repeats; - - repeat_pattern = TRUE; - - n_repeats = (gint) floor (image_area.width / surface_width); - target_width = image_area.width / n_repeats; - } - else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_SPACE) - { - cairo_surface_t *spaced_surface; - - spaced_surface = create_spaced_surface (surface, - surface_width, surface_height, - image_area.width, surface_height, - GTK_ORIENTATION_HORIZONTAL); - cairo_surface_destroy (surface); - surface = spaced_surface; - - /* short-circuit hscaling */ - target_width = surface_width = cairo_image_surface_get_width (spaced_surface); - } + case GTK_CSS_REPEAT_STYLE_REPEAT: + extend = CAIRO_EXTEND_REPEAT; + vscale = hscale; + break; + case GTK_CSS_REPEAT_STYLE_SPACE: + { + double space, n; + + extend = CAIRO_EXTEND_NONE; + vscale = hscale; + + ystep = vscale * slice_height; + n = floor (height / ystep); + space = (height - n * ystep) / (n + 1); + ystep += space; + y += space; + height -= 2 * space; + } + break; + case GTK_CSS_REPEAT_STYLE_NONE: + break; + case GTK_CSS_REPEAT_STYLE_ROUND: + extend = CAIRO_EXTEND_REPEAT; + vscale = height / (slice_height * MAX (round (height / (slice_height * hscale)), 1)); + break; + default: + g_assert_not_reached (); + break; } - else - { - image_area.x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right); - image_area.y = border_area->top; - image_area.width = (side == SIDE_LEFT) ? border_area->left : border_area->right; - image_area.height = total_height - border_area->top - border_area->bottom; - target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right); - target_y = border_area->top; + pattern = cairo_pattern_create_for_surface (slice); - if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_NONE) - { - target_height = total_height - border_area->top - border_area->bottom; - } - else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT) - { - repeat_pattern = TRUE; + cairo_matrix_init_translate (&matrix, + hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_width / 2 : 0, + vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_height / 2 : 0); + cairo_matrix_scale (&matrix, 1 / hscale, 1 / vscale); + cairo_matrix_translate (&matrix, + hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - width / 2 : 0, + vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - height / 2 : 0); - target_height = total_height - border_area->top - border_area->bottom; - target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right) / 2; - target_y = border_area->top + (total_height - border_area->top - border_area->bottom) / 2; - } - else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_ROUND) - { - gint n_repeats; + cairo_pattern_set_matrix (pattern, &matrix); + cairo_pattern_set_extend (pattern, extend); - repeat_pattern = TRUE; + cairo_save (cr); + cairo_translate (cr, x, y); - n_repeats = (gint) floor (image_area.height / surface_height); - target_height = image_area.height / n_repeats; - } - else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_SPACE) + for (y = 0; y < height; y += ystep) + { + for (x = 0; x < width; x += xstep) { - cairo_surface_t *spaced_surface; - - spaced_surface = create_spaced_surface (surface, - surface_width, surface_height, - surface_width, image_area.height, - GTK_ORIENTATION_VERTICAL); - cairo_surface_destroy (surface); - surface = spaced_surface; - - /* short-circuit vscaling */ - target_height = surface_height = cairo_image_surface_get_height (spaced_surface); + cairo_save (cr); + cairo_translate (cr, x, y); + cairo_set_source (cr, pattern); + cairo_rectangle (cr, 0, 0, xstep, ystep); + cairo_fill (cr); + cairo_restore (cr); } } - if (target_width == 0 || target_height == 0) - return; - - cairo_save (cr); - - pattern = cairo_pattern_create_for_surface (surface); - - gdk_cairo_rectangle (cr, &image_area); - cairo_clip (cr); - - cairo_translate (cr, - target_x, target_y); - - if (repeat_pattern) - cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); - else - cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); + cairo_restore (cr); - cairo_scale (cr, - target_width / surface_width, - target_height / surface_height); + cairo_pattern_destroy (pattern); +} - cairo_set_source (cr, pattern); - cairo_paint (cr); +static void +gtk_border_image_compute_slice_size (GtkBorderImageSliceSize sizes[3], + int surface_size, + int start_size, + int end_size) +{ + sizes[0].size = MIN (start_size, surface_size); + sizes[0].offset = 0; - cairo_restore (cr); + sizes[2].size = MIN (end_size, surface_size); + sizes[2].offset = surface_size - sizes[2].size; - cairo_pattern_destroy (pattern); - cairo_surface_destroy (surface); + sizes[1].size = MAX (0, surface_size - sizes[0].size - sizes[2].size); + sizes[1].offset = sizes[0].size; } void @@ -434,7 +361,10 @@ _gtk_border_image_render (GtkBorderImage *image, gdouble height) { cairo_surface_t *surface, *slice; - gdouble slice_width, slice_height, surface_width, surface_height; + GtkBorderImageSliceSize vertical_slice[3], horizontal_slice[3]; + GtkBorderImageSliceSize vertical_border[3], horizontal_border[3]; + int surface_width, surface_height; + int h, v; if (cairo_pattern_get_type (image->source) != CAIRO_PATTERN_TYPE_SURFACE) { @@ -463,147 +393,59 @@ _gtk_border_image_render (GtkBorderImage *image, surface_height = cairo_image_surface_get_height (surface); } - cairo_save (cr); - cairo_translate (cr, x, y); - - if ((image->slice.left + image->slice.right) < surface_width) + gtk_border_image_compute_slice_size (horizontal_slice, + surface_width, + image->slice.left, + image->slice.right); + gtk_border_image_compute_slice_size (vertical_slice, + surface_height, + image->slice.top, + image->slice.bottom); + gtk_border_image_compute_border_size (horizontal_border, + x, + width, + border_width->left, + border_width->right); + gtk_border_image_compute_border_size (vertical_border, + y, + height, + border_width->top, + border_width->bottom); + + for (v = 0; v < 3; v++) { - /* Top side */ - slice_width = surface_width - image->slice.left - image->slice.right; - slice_height = image->slice.top; - slice = cairo_surface_create_for_rectangle - (surface, - image->slice.left, 0, - slice_width, slice_height); - - render_border (cr, - width, height, - slice, - slice_width, slice_height, - SIDE_TOP, - border_width, - &image->repeat); - - cairo_surface_destroy (slice); - - /* Bottom side */ - slice_height = image->slice.bottom; - slice = cairo_surface_create_for_rectangle - (surface, - image->slice.left, surface_height - image->slice.bottom, - slice_width, slice_height); - - render_border (cr, - width, height, - slice, - slice_width, slice_height, - SIDE_BOTTOM, - border_width, - &image->repeat); - - cairo_surface_destroy (slice); - } + if (vertical_slice[v].size == 0 || + vertical_border[v].size == 0) + continue; - if ((image->slice.top + image->slice.bottom) < surface_height) - { - /* Left side */ - slice_width = image->slice.left; - slice_height = surface_height - image->slice.top - image->slice.bottom; - slice = cairo_surface_create_for_rectangle - (surface, - 0, image->slice.top, - slice_width, slice_height); - - render_border (cr, - width, height, - slice, - slice_width, slice_height, - SIDE_LEFT, - border_width, - &image->repeat); - - cairo_surface_destroy (slice); - - /* Right side */ - slice_width = image->slice.right; - slice = cairo_surface_create_for_rectangle - (surface, - surface_width - image->slice.right, image->slice.top, - slice_width, slice_height); - - render_border (cr, - width, height, - slice, - slice_width, slice_height, - SIDE_RIGHT, - border_width, - &image->repeat); - - cairo_surface_destroy (slice); + for (h = 0; h < 3; h++) + { + if (horizontal_slice[h].size == 0 || + horizontal_border[v].size == 0) + continue; + + if (h == 1 && v == 1) + continue; + + slice = cairo_surface_create_for_rectangle (surface, + horizontal_slice[h].offset, + vertical_slice[v].offset, + horizontal_slice[h].size, + vertical_slice[v].size); + + /* xxx: we scale to border-width here, that's wrong, isn't it? */ + gtk_border_image_render_slice (cr, + slice, + horizontal_slice[h].size, + vertical_slice[v].size, + horizontal_border[h].offset, + vertical_border[v].offset, + horizontal_border[h].size, + vertical_border[v].size, + h == 1 ? image->repeat.hrepeat : GTK_CSS_REPEAT_STYLE_NONE, + v == 1 ? image->repeat.vrepeat : GTK_CSS_REPEAT_STYLE_NONE); + + cairo_surface_destroy (slice); + } } - - /* Top/left corner */ - slice_width = image->slice.left; - slice_height = image->slice.top; - slice = cairo_surface_create_for_rectangle - (surface, - 0, 0, - slice_width, slice_height); - - render_corner (cr, - 0, 0, - border_width->left, border_width->top, - slice, - slice_width, slice_height); - - cairo_surface_destroy (slice); - - /* Top/right corner */ - slice_width = image->slice.right; - slice = cairo_surface_create_for_rectangle - (surface, - surface_width - image->slice.right, 0, - slice_width, slice_height); - - render_corner (cr, - width - border_width->right, 0, - border_width->right, border_width->top, - slice, - slice_width, slice_height); - - cairo_surface_destroy (slice); - - /* Bottom/left corner */ - slice_width = image->slice.left; - slice_height = image->slice.bottom; - slice = cairo_surface_create_for_rectangle - (surface, - 0, surface_height - image->slice.bottom, - slice_width, slice_height); - - render_corner (cr, - 0, height - border_width->bottom, - border_width->left, border_width->bottom, - slice, - slice_width, slice_height); - - cairo_surface_destroy (slice); - - /* Bottom/right corner */ - slice_width = image->slice.right; - slice = cairo_surface_create_for_rectangle - (surface, - surface_width - image->slice.right, - surface_height - image->slice.bottom, - slice_width, slice_height); - - render_corner (cr, - width - border_width->right, height - border_width->bottom, - border_width->right, border_width->bottom, - slice, - slice_width, slice_height); - - cairo_surface_destroy (slice); - - cairo_restore (cr); } -- 2.30.2